package com.xinqi.common.satoken.utils;

import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.listener.SaTokenEventCenter;
import cn.dev33.satoken.same.SaSameUtil;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.session.TokenSign;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.StpUtil;
import com.xinqi.common.base.error.CommonErrors;
import com.xinqi.common.base.user.Account;
import com.xinqi.common.core.enums.DeviceType;
import com.xinqi.common.core.enums.UserStatus;
import com.xinqi.common.core.utils.SpringUtils;
import com.xinqi.common.redis.utils.Caches;
import com.xinqi.common.satoken.constants.SaTokenConstant;
import com.xinqi.modules.user.user.dto.rsp.UserResultDTO;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.redisson.api.RedissonClient;

import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * 登录鉴权助手
 * <p>
 * user_type 为 用户类型 同一个用户表 可以有多种用户类型 例如 pc,app
 * deivce 为 设备类型 同一个用户类型 可以有 多种设备类型 例如 web,ios
 * 可以组成 用户类型与设备类型多对多的 权限灵活控制
 * <p>
 * 多用户体系 针对 多种用户类型 但权限控制不一致
 * 可以组成 多用户类型表与多设备类型 分别控制权限
 *
 * @author dgyu
 */
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class LoginHelper {
    private static final String ACCOUNT_KEY = "session:act:%s";
    private static final RedissonClient redissonClient = SpringUtils.getBean(RedissonClient.class);

    /**
     * 是否需要校验APP登录
     */
    public static final boolean IS_CHECK_APP_LOGIN = true;

    /**
     * 登录系统 基于 设备类型
     * 针对相同用户体系不同设备
     *
     * @param ueUser 登录用户信息
     */
    public static void loginXqByDevice(UserResultDTO ueUser, DeviceType deviceType) {
        loginXqByDevice(user2Account(ueUser), deviceType);
    }

    public static void loginXqByDevice(Account account, DeviceType deviceType) {
        Long userId = account.getId();
        // 校验用户状态
        CommonErrors.BAD_REQUEST.check(Objects.equals(account.getStatus(), UserStatus.OK.getCode()), "用户已被禁用");
        // 校验登录
        checkLogin(userId, deviceType);
        // 用户登录
        StpUtil.login(userId, deviceType.getDevice());
        // 保存用户信息
        Caches.set(account, getTimeOut(), redissonClient, ACCOUNT_KEY, account.getId());
        log.info("======> User login, userId = {}, deviceType = {}", userId, deviceType.getDevice());
    }

    private static long getTimeOut() {
        return StpUtil.getStpLogic().getConfig().getTimeout();
    }

    /**
     * 退出登录，将当前用户的所有设备全部踢下线
     *
     * @param loginId 用户ID
     */
    public static void logout(Object loginId) {
        StpUtil.logout(loginId);
        Caches.del(redissonClient, ACCOUNT_KEY, loginId);
    }

    /**
     * 退出登录，将当前用户的对应设备踢下线
     *
     * @param loginId 用户ID
     * @param device  设备类型
     */
    public static void logout(Object loginId, String device) {
        StpUtil.logout(loginId, device);
    }

    /**
     * 校验APP登录
     */
    public static void checkLogin(Object loginId, DeviceType deviceType) {
        if (!IS_CHECK_APP_LOGIN) {
            return;
        }
        // 校验APP端登录
        if (Objects.equals(deviceType, DeviceType.IOS) || Objects.equals(deviceType, DeviceType.ANDROID)) {
            checkLogin(loginId,
                v -> Objects.equals(v.getDevice(), DeviceType.IOS.getDevice()) || Objects.equals(v.getDevice(), DeviceType.ANDROID.getDevice()));
            // 校验PC端
        } else if (Objects.equals(deviceType, DeviceType.PC)) {
            checkLogin(loginId,
                v -> Objects.equals(v.getDevice(), DeviceType.PC.getDevice()));
        }
    }

    private static void checkLogin(Object loginId, Predicate<TokenSign> predicate) {
        SaSession session = StpUtil.getSessionByLoginId(loginId, false);
        if (session == null) {
            return;
        }
        // 获取当前所有的APP登录设备
        List<TokenSign> list = session.tokenSignListCopy().stream()
            .filter(predicate)
            .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(list)) {
            return;
        }
        StpLogic stpLogic = StpUtil.getStpLogic();
        // 遍历操作，移除之前登录设备
        for (TokenSign tokenSign : list) {
            // 清理： token签名、token最后活跃时间
            String tokenValue = tokenSign.getValue();
            session.removeTokenSign(tokenValue);
            // 删除Token-Id映射 & 清除Token-Session
            stpLogic.deleteTokenToIdMapping(tokenValue);
            stpLogic.deleteTokenSession(tokenValue);
            // $$ 发布事件：每次被顶下线时触发
            SaTokenEventCenter.doReplaced(stpLogic.getLoginType(), loginId, tokenValue);
            log.info("======> User logout, userId = {}, deviceType = {}", loginId, tokenSign.getDevice());
        }
        // 如果所有设备都注册了，则删除Session
        session.logoutByTokenSignCountToZero();
    }

    public static void checkLogin() {
        StpUtil.checkLogin();
    }

    /**
     * 获取用户id
     */
    public static Long getUserId() {
        Object userId = StpUtil.getLoginId();
        return Long.parseLong(userId.toString());
    }

    /**
     * 获取新奇课堂登录用户
     */
    public static Account getXqLoginUser() {
        // 获取Session
        Long userId = getUserId();
        // 获取用户信息
        return Caches.get(redissonClient, ACCOUNT_KEY, userId);
    }

    public static Account getXqLoginUser(Long userId) {
        // 获取用户信息
        return Caches.get(redissonClient, ACCOUNT_KEY, userId);
    }

    /**
     * 更新新奇课堂登录用户
     */
    public static void updateXqLoginUser(Account account) {
        // 保存用户信息
        Caches.setIfExist(account, getTimeOut(), redissonClient, ACCOUNT_KEY, account.getId());
    }

    /**
     * 刷新新奇课堂登录用户时长
     */
    public static void updateTokenTimer(UserResultDTO userBase) {
        // 更新token信息
        StpUtil.renewTimeout(getTimeOut());
        // 更新session信息
        //StpUtil.getSessionByLoginId(userBase.getId(), false).updateMinTimeout(getTimeOut());
        Caches.set(user2Account(userBase), getTimeOut(), redissonClient, ACCOUNT_KEY, userBase.getId());
        // 保存用户信息
        Caches.setTimeToLive(getTimeOut(), redissonClient, ACCOUNT_KEY, userBase.getId());
    }

    /**
     * 获取用户账户
     */
    public static String getUsername() {
        return getXqLoginUser().getUsername();
    }

    public static String getNickname() {
        return getXqLoginUser().getNickname();
    }

    /**
     * 是否为管理员
     */
    public static boolean isAdmin() {
        return getXqLoginUser().admin();
    }

    /**
     * 是否为超级管理员
     */
    public static boolean isSuperAdmin() {
        return getXqLoginUser().superAdmin();
    }

    /**
     * 校验SaSameToken （替换： SaSameUtil.checkCurrentRequestToken()）
     */
    public static void checkSameToken() {
        if (Objects.equals(SaHolder.getRequest().getHeader(SaSameUtil.SAME_TOKEN)
            , SaTokenConstant.SAME_TOKEN_VALUE)) {
            return;
        }
        throw CommonErrors.BAD_REQUEST.asException("服务器繁忙，请稍后重试");
    }

    public static Account user2Account(UserResultDTO user) {
        Account account = new Account();
        account.setId(user.getId());
        account.setUsername(user.getUsername());
        account.setNickname(user.getNickname());
        account.setStatus(user.getStatus());
        account.setIsAdmin(user.getIsAdmin());
        account.setIsSuperAdmin(user.getIsSuperAdmin());
        return account;
    }

}
